package com.shxzz.easy12306.services.user.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.shxzz.easy12306.framework.starter.cache.StringRedisTemplateProxy;
import com.shxzz.easy12306.framework.starter.convention.exceptions.ClientException;
import com.shxzz.easy12306.framework.starter.convention.exceptions.ServiceException;
import com.shxzz.easy12306.framework.starter.designpattern.chain.AbstractChainContext;
import com.shxzz.easy12306.framework.starter.user.core.UserInfoDTO;
import com.shxzz.easy12306.framework.starter.user.toolkit.JWTUtil;
import com.shxzz.easy12306.services.user.common.constant.RedisKeyConstant;
import com.shxzz.easy12306.services.user.common.constant.UserLoginConstant;
import com.shxzz.easy12306.services.user.common.enums.UserChainMark;
import com.shxzz.easy12306.services.user.common.enums.UserRegisterErrorCode;
import com.shxzz.easy12306.services.user.dao.entity.UserDO;
import com.shxzz.easy12306.services.user.dao.entity.UserEmailDO;
import com.shxzz.easy12306.services.user.dao.entity.UserPhoneDO;
import com.shxzz.easy12306.services.user.dao.entity.UserReuseDO;
import com.shxzz.easy12306.services.user.dao.mapper.UserEmailMapper;
import com.shxzz.easy12306.services.user.dao.mapper.UserMapper;
import com.shxzz.easy12306.services.user.dao.mapper.UserPhoneMapper;
import com.shxzz.easy12306.services.user.dao.mapper.UserReuseMapper;
import com.shxzz.easy12306.services.user.dto.req.UserLoginReqDTO;
import com.shxzz.easy12306.services.user.dto.req.UserRegisterReqDTO;
import com.shxzz.easy12306.services.user.dto.resp.UserLoginRespDTO;
import com.shxzz.easy12306.services.user.dto.resp.UserRegisterRespDTO;
import com.shxzz.easy12306.services.user.service.UserLoginService;
import com.shxzz.easy12306.services.user.toolkit.UserReuseUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * 用户登录接口实现
 *
 * @author shxzz
 * @create 2024-02-27-21:07
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class UserLoginServiceImpl implements UserLoginService {

    private final AbstractChainContext abstractChainContext;
    private final RedissonClient redissonClient;
    private final RBloomFilter<String> userRegisterBloomFilter;
    private final StringRedisTemplateProxy stringRedisTemplateProxy;

    private final UserMapper userMapper;
    private final UserPhoneMapper userPhoneMapper;
    private final UserEmailMapper userEmailMapper;
    private final UserReuseMapper userReuseMapper;

    private static final Pattern emailPattern = Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");
    private static final Pattern phonePattern = Pattern.compile("^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$");


    @Override
            public UserLoginRespDTO login(UserLoginReqDTO requestParam) {
        String usernameOrMailOrPhone = requestParam.getUsernameOrMailOrPhone();

        String username = null;
        if (emailPattern.matcher(usernameOrMailOrPhone).matches()) {
            LambdaQueryWrapper<UserEmailDO> queryWrapper = Wrappers.lambdaQuery(UserEmailDO.class)
                    .eq(UserEmailDO::getUsername, usernameOrMailOrPhone);
            username = Optional.ofNullable(userEmailMapper.selectOne(queryWrapper))
                    .map(UserEmailDO::getUsername)
                    .orElseThrow(() -> new ClientException("账号或密码不正确"));
        } else if (phonePattern.matcher(usernameOrMailOrPhone).matches()) {
            LambdaQueryWrapper<UserPhoneDO> queryWrapper = Wrappers.lambdaQuery(UserPhoneDO.class)
                    .eq(UserPhoneDO::getPhone, usernameOrMailOrPhone);
            username = Optional.ofNullable(userPhoneMapper.selectOne(queryWrapper))
                    .map(UserPhoneDO::getUsername)
                    .orElseThrow(() -> new ClientException("账号或密码不正确"));
        }

        if(username == null) username = usernameOrMailOrPhone;

        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, username)
                .eq(UserDO::getPassword, requestParam.getPassword());
        UserDO userDO = userMapper.selectOne(queryWrapper);

        if(userDO == null) {
            throw new ClientException("账号或密码不正确");
        }
        UserInfoDTO userInfoDTO = UserInfoDTO.builder()
                .userId(String.valueOf(userDO.getId()))
                .username(userDO.getUsername())
                .realName(userDO.getRealName())
                .build();
        String accessToken = JWTUtil.generateAccessToken(userInfoDTO);
        UserLoginRespDTO result = new UserLoginRespDTO()
                .setUserId(userInfoDTO.getUserId())
                .setUsername(userInfoDTO.getUsername())
                .setRealName(userInfoDTO.getRealName())
                .setAccessToken(accessToken);
        stringRedisTemplateProxy.put(accessToken, JSON.toJSON(result), UserLoginConstant.USER_LOGIN_HOLD_TIME, TimeUnit.MINUTES);
        return result;
    }

    @Override
    public UserLoginRespDTO checkLogin(String accessToken) {
        return stringRedisTemplateProxy.get(accessToken, UserLoginRespDTO.class);
    }

    @Override
    public void logout(String accessToken) {
        stringRedisTemplateProxy.delete(accessToken);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public UserRegisterRespDTO register(UserRegisterReqDTO requestParam) {
        // 必要参数非空、用户名可用
        abstractChainContext.handler(UserChainMark.USER_REGISTER_CHAIN_FILTER.name(), requestParam);

        String username = requestParam.getUsername();
        RLock lock = redissonClient.getLock(RedisKeyConstant.LOCK_USER_REGISTER_PREFIX + username);
        if(!lock.tryLock()) {
            throw new ClientException(UserRegisterErrorCode.HAS_USERNAME_NOTNULL);
        }
        try {
            UserDO userDO = BeanUtil.toBean(requestParam, UserDO.class);
            try {
                int insert = userMapper.insert(userDO);
                if(insert < 1) {
                    throw new ServiceException(UserRegisterErrorCode.USER_REGISTER_FAIL);
                }
            } catch (DuplicateKeyException ex) {
                log.error("用户名 [{}] 重复注册", userDO.getUsername());
                throw new ClientException(UserRegisterErrorCode.HAS_USERNAME_NOTNULL);
            }
            UserPhoneDO userPhoneDO = UserPhoneDO.builder()
                    .phone(userDO.getPhone())
                    .username(username)
                    .build();
            try {
                userPhoneMapper.insert(userPhoneDO);
            } catch (DuplicateKeyException e) {
                log.error("用户 [{}] 注册手机号 [{}] 重复", username, userPhoneDO.getPhone());
                throw new ClientException(UserRegisterErrorCode.PHONE_REGISTERED);
            }
            Optional.ofNullable(requestParam.getEmail())
                    .filter(email -> !CharSequenceUtil.isEmpty(email))
                    .ifPresent(email -> {
                        UserEmailDO userEmailDO = UserEmailDO.builder()
                                .username(username)
                                .email(email)
                                .build();
                        try {
                            userEmailMapper.insert(userEmailDO);
                        } catch (DuplicateKeyException e) {
                            log.error("用户 [{}] 注册邮箱 [{}] 重复", username, userEmailDO.getEmail());
                            throw new ClientException(UserRegisterErrorCode.MAIL_REGISTERED);
                        }
                    });

            LambdaUpdateWrapper<UserReuseDO> userReuseDOLambdaUpdateWrapper = Wrappers.lambdaUpdate(UserReuseDO.class)
                    .eq(UserReuseDO::getUsername, username)
                    .eq(UserReuseDO::getDelFlag, 0);
            userReuseMapper.delete(userReuseDOLambdaUpdateWrapper);

            StringRedisTemplate stringRedisTemplate = stringRedisTemplateProxy.getInstance();
            stringRedisTemplate.opsForSet().remove(getUsernameReuseSetKey(username), username);

            userRegisterBloomFilter.add(username);

        } finally {
            lock.unlock();
        }

        return BeanUtil.toBean(requestParam, UserRegisterRespDTO.class);
    }

    @Override
    public Boolean isUsernameAvailable(String username) {
        if (userRegisterBloomFilter.contains(username)) {
            StringRedisTemplate stringRedisTemplate = stringRedisTemplateProxy.getInstance();
            return stringRedisTemplate.opsForSet()
                    .isMember(getUsernameReuseSetKey(username), username);
        }
        return Boolean.TRUE;
    }

    private String getUsernameReuseSetKey(String username) {
        return RedisKeyConstant.USERNAME_REUSE_SET_PREFIX + UserReuseUtil.getUsernameReuseShardingIdx(username);
    }


}
